Unity IAPを用いたAndroidのコンビニ課金について


概要

TL;DR

Googleのドキュメントは間違っている状態で放置されている、投げ捨てろ。

Unityのフォーラムを見ろ。



Androidにはコンビニ課金ってのがあって、Unity IAPも当然それに対応しているよ!! ってはなし。

ただ、、たださあ、、、Googleのドキュメントがおかしいんだわ。

間違った情報が公式として残り続けている。


そしてテストにもやばいレベルの問題がある。テストできねーのこれ。

実機でお支払い or キャンセル。何、その、、、19世紀か?


んでどんなフローになるのかとかの資料もないのな。

リリースされたの2019年なのにマジで資料ない! すげえ。

日本とメキシコ以外に対応してる国増えてると思うんだけどなー。


そして拒否してるゲームの多いこと。いやナチュラルに対応拒否させてくれ、、、


Google Play Billing Library 3.0に対応しないといけなかったのが去年だっけ、一昨年だっけ、

そこから3系に対応すると、もれなくコンビニ課金機能がオンになる。

デフォルトでオンなので。

で、これをオフにするか、対応するか、これはそんなはなし。


ちなみにUnity IAP 2.2.x系以降は対応している方法になっている。つまりずっっっっっっっと前から、Unity IAPは単体でコンビニ課金に対応できている。

ドキュメントを探す術がどこにもねーことを除けば、、、


あっちなみにUnity IAPの公式ドキュメントはこれで、ここ見るとあるけど

https://docs.unity3d.com/Packages/com.unity.purchasing@3.0/api/UnityEngine.Purchasing.IGooglePlayStoreExtensions.html


コンビニ決済からここへのルートは多分誰も書いてくれてない。今回のこの記事の役割は、このミッシングリンクを埋めること。



説明

コンビニ決済は次のようなフローを辿るアレである。

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. ここで[App戻る]という選択肢があるが、それを押すと、IStoreListenerで書かれてる必須なハンドラのみを扱っているようなUnity IAPの課金処理がしっかりばっちりハングする。

4. 実際にはこうするといけるよ的なハンドラを指定することで回避が可能だが、その資料がXXXXXなんだなあ、、、というアレ。



テスト方法について

コンビニ決済は2019年に生まれたPlayStore特有の決済方法だ、

さて2019年といえば2年も前なわけだから、当然PlayStoreも何かしら気の利いたデバッグ方法を用意している。紹介しよう。


ないんだなあ。ないの。実機で、実アカウントでゴーするしかないの。 

なので体当たりでどういう遷移になるのかを追うしかなかった。


それできっと、この記事を読んだマトモなひとが、いやこうやったらテストできましたよ、お家から快適生活ですよ、とか

そういうことを書いてくれることを、心から願ってやまないよ。



ちなみに豆知識として、PlayStoreに登録したテスト課金ユーザーだとコンビニ決済自体が表示されないの。どうして。


実際に失敗したルートから書いていく。



wrong end route

Googleでdeffered purchase周りを調べると、次の記事に行きあたる。

https://developer.android.com/google/play/billing/unity

(英語でも日本語でも内容が一緒)


で、迷わせる前に書くと、これが、Unity IAPの何系を使っていてもうまく動くことはない。

関わるな。これはゴミだ。


BAD END。



true end route

Unityのフォーラム曰く、

https://forum.unity.com/threads/googleplaystoremodule-does-not-exist-in-the-namespace-after-unity-update.1076585/


ここの最後で

スクリーンショット 2021-08-19 11.11.21.png

Unityの中の人が「そのへんのGoogleの用意してるアレやらんでいいやつやで」って言及していて、


これを見るといいよっていうサンプルコードのリンクが別のフォーラムにあるよって書いてあって、

https://forum.unity.com/threads/sample-iap-project.529555/


そのフォーラムのページにいくとさらっとSampleProjectのzipのリンクがあり、、、

スクリーンショット 2021-08-19 14.50.45.png


えー、このプロジェクトを見ると次のようなコードがあって

    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)

    {

        MyDebug("OnInitialized: PASS");


        m_StoreController = controller;

        m_StoreExtensionProvider = extensions;

        m_AppleExtensions = extensions.GetExtension<IAppleExtensions>();

        m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();


        m_GoogleExtensions?.SetDeferredPurchaseListener(OnPurchaseDeferred);


        Dictionary<string, string> dict = m_AppleExtensions.GetIntroductoryPriceDictionary();


        foreach (UnityEngine.Purchasing.Product item in controller.products.all)

        {


            if (item.receipt != null)

            {

                string intro_json = (dict == null || !dict.ContainsKey(item.definition.storeSpecificId)) ? null : dict[item.definition.storeSpecificId];


                if (item.definition.type == ProductType.Subscription)

                {

                    SubscriptionManager p = new SubscriptionManager(item, intro_json);

                    SubscriptionInfo info = p.getSubscriptionInfo();

                    MyDebug("SubInfo: " + info.getProductId().ToString());

                    MyDebug("isSubscribed: " + info.isSubscribed().ToString());

                    MyDebug("isFreeTrial: " + info.isFreeTrial().ToString());

                }

            }

        }

    }


特筆すべきは

        m_GoogleExtensions = extensions.GetExtension<IGooglePlayStoreExtensions>();


        m_GoogleExtensions?.SetDeferredPurchaseListener(OnPurchaseDeferred);


と、セットされているOnPurchaseDefferred関数


    public void OnPurchaseDeferred(Product product)

    {


        MyDebug("Deferred product " + product.definition.id.ToString());

    }


でーーー、、


この関数があると、Unity IAPを使った状態でコンビニ決済にも対応できる。

この関数は、次のタイミングで着火する



パターン1 支払い前App戻り

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. ここで[App戻る]という選択肢があるが、それを押すと、OnPurchaseDefferredが着火する

ここは課金前、キャンセル前、というものすごく中途半端な状態で、なおかつこの関数はUnityのmain threadとは別のスレッドで着火することに注意。

ここで「あーーコンビニ決済なんかつかっちゃたンゴねえ、、払い込み終わったらポイント付与します」というようなことをユーザーに伝えることができる。

ちなみにここで取得できるproductにはreceiptは付いてないです。 付けてからよこせよPlayStore、、、pendingでいいからさあ、、、

先に言っとくが、OnPurchaseDefferredは、「ユーザーがコンビニ課金を使ったことをユーザーやサーバに知らせるためにある」ようなハンドラです。それ以外の価値はない。


おそらく最も頻度が高く、かつ誰も救わないルート。というか他ルートの体験はさらに悪いんだが。


パターン2-a 支払い後App戻り 早すぎた場合

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. Appをほったらかしてコンビニに行って決済する。10分 ~ 48時間で決済が完了するよ!!とか言われる

4. よくわからんなあと思ってでも決済したし早速購入結果見れるんじゃね?と思ってAppに戻ると、OnPurchaseDefferredが着火する。

5. ここからさらにだいたい決済から10分くらいほっとくと、何事もなかったかのようにIStoreListenerのProcessPurchase関数がよばれ、レシートが手に入る。

お金を払ったのに付与されるのは10分後です。ここでのOnPurchaseDefferredにはreceiptがあったりなかったりするが、正常系が動くのでもうどうでもいいというおまけ付き。


頻度第二位。すまない、まだなんだ。10分っつったら大体ほんとに10分かかるんだよこれ。いや日本語なんだけど、なんだけどさ、いやあ、、、

実際には5~8分くらいが中央値な気がします。するだけだ。1秒とかにならん?


パターン2-b 支払い後App戻り 10分待った場合

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. Appをほったらかしてコンビニに行って決済する。10分 ~ 48時間で決済が完了するよ!!とか言われる

4. よくわからんなあと思ってでも決済から10分待ち、Appに戻ると、何事もなかったかのようにIStoreListenerのProcessPurchase関数がよばれ、レシートが手に入る。

こんなルート踏むやついる?? いや常識的に考えてくれよ、金払ってもさらに待たされるんだぜ?


一番幸せになれる、最もレアなルート。10分待つけど、それをユーザーに説明できて、納得されることってあんの?


パターン2-c 支払い後App戻りしようと思ったらAppが落ちてて、10分待った場合

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. Appをほったらかしてコンビニに行って決済する。10分 ~ 48時間で決済が完了するよ!!とか言われる

4. よくわからんなあと思ってでも決済から10分待ち、Appに戻ると、おやおやおや、起動画面ですか、そうAppはなんらかの理由で落ちていたのだ。 あり得る話だろ?

5. IStoreListenerのProcessPurchase関数がよばれ、レシートが手に入る。

6. このレシートを検証すると、purchased状態であることがわかる。

なので対価を付与しましょうね。

ちなみに知らんうちにコンビニ決済対応しちゃってて一番多いのはこのパターンだと思う。

パターン3-a 支払い前AppもどりAppキル

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. ここで[App戻る]という選択肢があるが、戻ったところで「払ってね!」って言われるだけなので、Appキルするとしよう

4. キルしてから再度Appを起動し直すと、IStoreListenerのProcessPurchaseが呼ばれる。先程のコンビニ課金のレシートが手に入るんだねえ。

5. このレシートを検証すると、pending状態であることがわかる。

コンビニ決済は3日間の支払い猶予がある決済になっておりまして、

3日間支払われないとpending -> cancelledへと状態が変化しますの。


このルート以外で、コンビニ課金でサーバにpending状態なreceiptが届けられるルートは無い。

ないんだ。


パターン3-b 支払い前AppもどりAppキルPlayStoreキャンセル

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. ここで[App戻る]という選択肢があるが、戻ったところで「払ってね!」って言われるだけで何も起こらないので、はあ金払いにいくかあってなってまあ行かんだろう。

4. まだ君は払ってない、けど再度Appを起動し直すと、IStoreListenerのProcessPurchaseが呼ばれる。先程のコンビニ課金のレシートが手に入るんだねえ。

5. このレシートを検証すると、pending状態であることがわかる。

ここでサーバに送り込むチャンスがあることはすでに3-aで書いた。地獄はここからだ。

6. ここで、おもむろにPlayStore Appから、ユーザーアイコンをおして、[お支払いと定期購入] > [予算と履歴] から、購入をキャンセルしよう!どうなると思う?

7. キャンセルされたら、それ以降、IStoreListenerのProcessPurchaseが呼ばれることはない。

呼ばれないの当たり前じゃん、って思うよね。 まあね、サーバで検証とかしてなきゃこれで終わりでいいんだわ。

で、5でサーバに送ってる場合、


すでに送り込まれているpending状態のレシートを、クライアントからの更新があって検証する機会はもう生まれないんだ。

だからサーバサイドでバッチとかで対処しようね。


はい。バッチ必須です。 

もとから3日間の生存があるのでバッチ必須でしょってのはあるとしてさあ、PlayStoreからキャンセルになったよーってのが出りゃあ、それですんだんだけどなあ?


パターン4 支払い前PlayStoreキャンセル後App戻り

1. ユーザーがコンビニ決済を選んでApp内課金を行う = 購入するプロダクトを選ぶ

2. するとAppから別画面に切り替わり、「コンビニに行って決済しよう!」という画面にいく

3. ここでおもむろにPlayStore Appから、ユーザーアイコンをおして、[お支払いと定期購入] > [予算と履歴] から、購入をキャンセルしよう!どうなると思う?2

4. これ以降どこにも何のreceiptも飛ばなくなる。

キャンセルするならいっそAppに戻らなければいいのですわ。

えーーこれ以外にも色々あるが

とりあえずもういいだろ。


good end route

えー、コンビニ決済、正直、対応するうまみがないです。

なのでApp単位でGoogleに言って拒否するのが最もまともなエクスペリエンスを提供できるぞ。

どういうことだよ、、、って思うけどさあ、

このコロナ禍にコンビニまで行ってPlayCardを買う以外の手段がさらに5分以上ユーザーを待たせるのを正当化できないなーっておもった。


対応しない方が世界に良い。稀有稀。



補足

いろんなAppがコンビニ決済を蹴っている(対応していません)表示になるのだよな。きみはどう?



メモ


GooglePlayPluginsを追加

google-play-plugins-1.5.0.unitypackage

https://github.com/google/play-unity-plugins/releases



そもそもちゃんと動いてない。

Google > ウィンドウ、、からめっちゃエラーが出る。


これは結論から言うと誰も使わなくていいやつだった。

なんだこの公式ドキュメントは。


-> 古いまま対応されてないだけ。